﻿using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;

namespace Quickuse.Logging.FileLogger
{
    /// <summary>
    /// 文件日志处理器
    /// </summary>
    internal class FileLoggerProcessor : IDisposable
    {
        private readonly object _locker = new object();

        private readonly BlockingCollection<LogMessageEntry> _messageQueue;
        private readonly Thread _outputFileThread;

        private IFileLoggerSettings _fileLoggerSettings;
        private string _logFilePath;    //文件路径

        /// <summary>
        /// FileLoggerProcessor
        /// </summary>
        /// <param name="fileLoggerSettings">fileLoggerSettings</param>
        public FileLoggerProcessor(IFileLoggerSettings fileLoggerSettings)
        {
            _fileLoggerSettings = fileLoggerSettings;
            _messageQueue = new BlockingCollection<LogMessageEntry>(_fileLoggerSettings.MaxQueuedLength);

            Directory.CreateDirectory(_fileLoggerSettings.LogFloder);
            _logFilePath = Path.Combine(_fileLoggerSettings.LogFloder, _fileLoggerSettings.LogFileName);

            _outputFileThread = new Thread(ProcessLogQueue)
            {
                IsBackground = true,
                Name = "File logger queue processing thread"
            };
            _outputFileThread.Start();

            if (_fileLoggerSettings.ChangeToken != null)
            {
                _fileLoggerSettings.ChangeToken.RegisterChangeCallback(OnConfigurationReload, null);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="state"></param>
        private void OnConfigurationReload(object state)
        {
            try
            {
                // The settings object needs to change here, because the old one is probably holding on
                // to an old change token.
                _fileLoggerSettings = _fileLoggerSettings.Reload();
            }
            catch (Exception ex)
            {
                //System.Console.WriteLine($"Error while loading configuration changes.{Environment.NewLine}{ex}");
                InternalLog.Error(ex.Message);
            }
            finally
            {
                // The token will change each time it reloads, so we need to register again.
                if (_fileLoggerSettings?.ChangeToken != null)
                {
                    _fileLoggerSettings.ChangeToken.RegisterChangeCallback(OnConfigurationReload, null);
                }
            }
        }

        /// <summary>
        /// 入队消息
        /// </summary>
        /// <param name="message"></param>
        /// <param name="logLevel"></param>
        public void EnqueueMessage(string message, LogLevel logLevel)
        {
            if (_fileLoggerSettings.IsAsyn && !_messageQueue.IsAddingCompleted)
            {
                _messageQueue.Add(new LogMessageEntry { Message = message, logLevel = logLevel });
            }
            else
            {
                WriteToFile(message, logLevel);
            }
        }

        /// <summary>
        /// 处理消息
        /// </summary>
        public void ProcessLogQueue()
        {
            try
            {
                foreach (var message in _messageQueue.GetConsumingEnumerable())
                {
                    WriteToFile(message.Message, message.logLevel);
                }
            }
            catch
            {
                try
                {
                    _messageQueue.CompleteAdding();
                }
                catch { }
            }
        }

        /// <summary>
        /// WriteToFile
        /// </summary>
        /// <param name="logMessage">logMessage</param>
        /// <param name="logLevel">logLevel</param>
        private void WriteToFile(string logMessage, LogLevel logLevel)
        {
            RollFile();

            var newlogfilepath = _logFilePath;
            if (_fileLoggerSettings.FileClassify)
            {
                newlogfilepath = FindClassifyFilePath(_logFilePath, logLevel);
            }

            WriteToFile(newlogfilepath, logMessage);
        }

        /// <summary>
        /// 写入文件
        /// </summary>
        /// <param name="filePath">文件路径</param>
        /// <param name="content">内容</param>
        private void WriteToFile(string filePath, string content)
        {
            using (StreamWriter streamWriter = File.AppendText(filePath))
            {
                streamWriter.Write(content);
            }
        }

        /// <summary>
        /// RollFile
        /// </summary>
        private void RollFile()
        {
            lock (_locker)
            {
                if (_fileLoggerSettings.FileClassify)
                {
                    //文件分类
                    foreach (var logLevel in _fileLoggerSettings.Levels)
                    {
                        var classify_newfilepath = FindClassifyFilePath(_logFilePath, logLevel);

                        if (!File.Exists(classify_newfilepath)) return;

                        var fileinfo = new FileInfo(classify_newfilepath);

                        #region delete log file
                        var files = Directory.GetFiles(fileinfo.DirectoryName, $"*{fileinfo.Extension}");
                        var fis = files.Select(e => new FileInfo(e)).Where(e => e.LastAccessTime < DateTime.Now.AddDays(-_fileLoggerSettings.FileMaxBackup));
                        if (fis.Count() > 0)
                        {
                            foreach (var item in fis)
                            {
                                File.Delete(item.FullName);
                            }
                        }
                        #endregion

                        MoveFile(classify_newfilepath);
                    }
                }
                else
                {
                    if (!File.Exists(_logFilePath)) return;

                    var fileinfo = new FileInfo(_logFilePath);

                    #region delete log file
                    var files = Directory.GetFiles(fileinfo.DirectoryName, $"*{fileinfo.Extension}");
                    if (files.Count() > _fileLoggerSettings.FileMaxBackup)
                    {
                        var fis = files.Select(e => new FileInfo(e)).Where(e => e.LastAccessTime < DateTime.Now.AddDays(-_fileLoggerSettings.FileMaxBackup));
                        if (fis.Count() > 0)
                        {
                            foreach (var item in fis)
                            {
                                File.Delete(item.FullName);
                            }
                        }
                    }
                    #endregion

                    MoveFile(_logFilePath);
                }
            }
        }

        /// <summary>
        /// MoveFile
        /// </summary>
        /// <param name="filepath">filepath</param>
        private void MoveFile(string filepath)
        {
            if (File.Exists(filepath))
            {
                var fileinfo = new FileInfo(filepath);

                if (fileinfo.LastWriteTime.ToString("yyyy-MM-dd") == DateTime.Now.ToString("yyyy-MM-dd"))
                {
                    //检查文件大小(同一天)
                    if (fileinfo.Length >= _fileLoggerSettings.FileSize * 1024 * 1024)
                    {
                        var newfilename = FindNewFileName(fileinfo);
                        if (!File.Exists(newfilename))
                        {
                            fileinfo.MoveTo(newfilename);
                        }
                    }
                }
                else
                {
                    var newfilename = $"{fileinfo.DirectoryName}{Path.DirectorySeparatorChar}{fileinfo.Name.Replace(fileinfo.Extension, "")}_{fileinfo.LastWriteTime.ToString("yyyy-MM-dd")}{fileinfo.Extension}";
                    fileinfo.MoveTo(newfilename);
                }
            }
        }

        /// <summary>
        /// FindNewFileName 
        /// </summary>
        /// <param name="fileInfo">fileInfo</param>
        /// <returns></returns>
        private string FindNewFileName(FileInfo fileInfo)
        {
            var logFolderfiles = Directory.GetFiles(fileInfo.DirectoryName, $"*{fileInfo.Extension}");
            if (logFolderfiles.Length > 0)
            {
                var count = logFolderfiles.Select(e => new FileInfo(e))
                    .Where(e => e.LastWriteTime.ToString("yyyy-MM-dd") == DateTime.Now.ToString("yyyy-MM-dd"))
                    .Count(e => e.Name.Contains(fileInfo.Name.Replace(fileInfo.Extension, "")));

                var newfilename = $"{fileInfo.DirectoryName}{Path.DirectorySeparatorChar}{fileInfo.Name.Replace(fileInfo.Extension, "")}_{fileInfo.LastWriteTime.ToString("yyyy-MM-dd")}_{count}{fileInfo.Extension}";
                return newfilename;
            }
            else
            {
                var newfilename = $"{fileInfo.DirectoryName}{Path.DirectorySeparatorChar}{fileInfo.Name.Replace(fileInfo.Extension, "")}_{fileInfo.LastWriteTime.ToString("yyyy-MM-dd")}_1{fileInfo.Extension}";
                return newfilename;
            }
        }

        /// <summary>
        /// 获取分类文件路径
        /// </summary>
        /// <param name="_logFilePath">_logFilePath</param>
        /// <param name="logLevel">logLevel</param>
        /// <returns></returns>
        private string FindClassifyFilePath(string _logFilePath, LogLevel logLevel)
        {
            if (string.IsNullOrEmpty(_logFilePath))
            {
                return _logFilePath;
            }
            var fileinfo = new FileInfo(_logFilePath);
            var newfilepath = $"{fileinfo.DirectoryName}{Path.DirectorySeparatorChar}{fileinfo.Name.Replace(fileinfo.Extension, "")}_{logLevel.GetLogLevelString()}{fileinfo.Extension}";

            return newfilepath;
        }

        public void Dispose()
        {
            _messageQueue.CompleteAdding();

            try
            {
                _outputFileThread.Join(_fileLoggerSettings.TaskWaitTime); // with timeout in-case Console is locked by user input
            }
            catch (ThreadStateException) { }
        }
    }

    /// <summary>
    /// 日志条目
    /// </summary>
    internal struct LogMessageEntry
    {
        /// <summary>
        /// 
        /// </summary>
        public string Message { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public LogLevel logLevel { get; set; }
    }
}
